using System;
using System.Collections;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using eKeyboard =  EuphoricEngine3D.Input.Keyboard;

using LuaInterface;
using LuaNetInterface;

using EuphoricEngine3D;
using EuphoricEngine3D.Helpers;

namespace _EuphoricEngine3D.Components
{
    /// <summary>
    /// A component for handling and displaying a Lua-based scripting console window/component.
    /// </summary>
    [LuaPackageAttribute(null, null)]
    public class ConsoleComponent : DrawableGameComponent
    {
        #region Structures
        /// <summary>
        /// A structure for containing valid text characters to display.
        /// </summary>
        internal struct ValidCharacter
        {
            /// <summary>
            /// The standard character to display.
            /// </summary>
            public char Character;

            /// <summary>
            /// The character to display when the Shift key is pressed.
            /// </summary>
            public char ShiftedCharacter;

            /// <summary>
            /// Creates a valid text characters structure.
            /// </summary>
            /// <param name="ch">The standard character to display.</param>
            /// <param name="shiftCh">The character to display when the Shift key is pressed.</param>
            public ValidCharacter(char ch, char shiftCh)
            {
                Character = ch;
                ShiftedCharacter = shiftCh;
            }
        }
        #endregion

        #region Data Members
        #region Lua Scripting Fields
        /// <summary>
        /// The Lua scripting virtual machine.
        /// </summary>
        LuaVirtualMachine _lua;
        #endregion

        #region Graphics Fields
        /// <summary>
        /// The background texture for the console.
        /// </summary>
        private ResolveTexture2D _texture;

        /// <summary>
        /// The font for drawing the console text.
        /// </summary>
        private SpriteFont _font;

        /// <summary>
        /// The valid height of how far text can be scrolled up and down.
        /// </summary>
        private int _validDisplayHeight;

        /// <summary>
        /// The sprite batch for drawing the console.
        /// </summary>
        private SpriteBatch _spriteBatch;
        #endregion

        #region Menu Fields
        /// <summary>
        /// Whether or not the console is active.
        /// </summary>
        private bool _active;

        /// <summary>
        /// The width of the console.
        /// </summary>
        private int _width;

        /// <summary>
        /// The height of the console.
        /// </summary>
        private int _height;

        /// <summary>
        /// The offset of the log in pixels.
        /// </summary>
        private int _logOffset;

        /// <summary>
        /// The current command string.
        /// </summary>
        private string _command = "";

        /// <summary>
        /// The command string prefix.
        /// </summary>
        private const string _prefix = "> ";

        /// <summary>
        /// The separator line sequence.
        /// </summary>
        private const string _line = "---------------------\n";

        /// <summary>
        /// The starting help message.
        /// </summary>
        private const string _message = "Type 'help()' to begin";

        /// <summary>
        /// The list of log entries.
        /// </summary>
        private List<string> _log = new List<string>();
        #endregion

        #region Input Fields
        /// <summary>
        /// The valid characters that can be entered as input.
        /// </summary>
        private Hashtable _validChars;

        /// <summary>
        /// The input manager for the console.
        /// </summary>
        private eKeyboard _inputManager;
        #endregion
        #endregion

        #region Properties
        /// <summary>
        /// Gets whether or not the console is currently active.
        /// </summary>
        public bool IsActive
        {
            get { return _active; }
        }
        #endregion

        #region Initialization
        /// <summary>
        /// Creates the component and registers a Lua scripting virtual machine.
        /// </summary>
        /// <param name="game">The game containing the console window component.</param>
        /// <param name="lua">The Lua scripting virtual machine to use.</param>
        public ConsoleComponent(Game game, LuaVirtualMachine lua, eKeyboard keyboard)
            : base(game)
        {
            // If no Lua scripting virtual machine was passed in, create one
            if (lua == null)
                lua = new LuaVirtualMachine();

            _lua = lua;

            _inputManager = keyboard;
        }

        /// <summary>
        /// Initializes the component and registers necessary data.
        /// </summary>
        public override void Initialize()
        {
            _log.Add(_message);
            _logOffset = 0;

            _lua.RegisterLuaFunctions(this);

            RegisterValidKeys();

            base.Initialize();
        }

        /// <summary>
        /// Registers the valid characters to display and keys that can be pressed in the console.
        /// </summary>
        private void RegisterValidKeys()
        {
            _validChars = new Hashtable();

            // Register standard letters
            _validChars.Add(Keys.A, new ValidCharacter('a', 'A'));
            _validChars.Add(Keys.B, new ValidCharacter('b', 'B'));
            _validChars.Add(Keys.C, new ValidCharacter('c', 'C'));
            _validChars.Add(Keys.D, new ValidCharacter('d', 'D'));
            _validChars.Add(Keys.E, new ValidCharacter('e', 'E'));
            _validChars.Add(Keys.F, new ValidCharacter('f', 'F'));
            _validChars.Add(Keys.G, new ValidCharacter('g', 'G'));
            _validChars.Add(Keys.H, new ValidCharacter('h', 'H'));
            _validChars.Add(Keys.I, new ValidCharacter('i', 'I'));
            _validChars.Add(Keys.J, new ValidCharacter('j', 'J'));
            _validChars.Add(Keys.K, new ValidCharacter('k', 'K'));
            _validChars.Add(Keys.L, new ValidCharacter('l', 'L'));
            _validChars.Add(Keys.M, new ValidCharacter('m', 'M'));
            _validChars.Add(Keys.N, new ValidCharacter('n', 'N'));
            _validChars.Add(Keys.O, new ValidCharacter('o', 'O'));
            _validChars.Add(Keys.P, new ValidCharacter('p', 'P'));
            _validChars.Add(Keys.Q, new ValidCharacter('q', 'Q'));
            _validChars.Add(Keys.R, new ValidCharacter('r', 'R'));
            _validChars.Add(Keys.S, new ValidCharacter('s', 'S'));
            _validChars.Add(Keys.T, new ValidCharacter('t', 'T'));
            _validChars.Add(Keys.U, new ValidCharacter('u', 'U'));
            _validChars.Add(Keys.V, new ValidCharacter('v', 'V'));
            _validChars.Add(Keys.W, new ValidCharacter('w', 'W'));
            _validChars.Add(Keys.X, new ValidCharacter('x', 'X'));
            _validChars.Add(Keys.Y, new ValidCharacter('y', 'Y'));
            _validChars.Add(Keys.Z, new ValidCharacter('z', 'Z'));

            // Register numeric characters and corresponding symbols
            _validChars.Add(Keys.D1, new ValidCharacter('1', '!'));
            _validChars.Add(Keys.D2, new ValidCharacter('2', '"'));
            _validChars.Add(Keys.D3, new ValidCharacter('3', '#'));
            _validChars.Add(Keys.D4, new ValidCharacter('4', '$'));
            _validChars.Add(Keys.D5, new ValidCharacter('5', '%'));
            _validChars.Add(Keys.D6, new ValidCharacter('6', '^'));
            _validChars.Add(Keys.D7, new ValidCharacter('7', '&'));
            _validChars.Add(Keys.D8, new ValidCharacter('8', '*'));
            _validChars.Add(Keys.D9, new ValidCharacter('9', '('));
            _validChars.Add(Keys.D0, new ValidCharacter('0', ')'));

            // Register special characters
            _validChars.Add(Keys.Space, new ValidCharacter(' ', ' '));
            _validChars.Add(Keys.OemTilde, new ValidCharacter('`', '~'));
            _validChars.Add(Keys.OemMinus, new ValidCharacter('-', '_'));
            _validChars.Add(Keys.OemPlus, new ValidCharacter('=', '+'));
            _validChars.Add(Keys.OemOpenBrackets, new ValidCharacter('[', '{'));
            _validChars.Add(Keys.OemCloseBrackets, new ValidCharacter(']', '}'));
            _validChars.Add(Keys.OemBackslash, new ValidCharacter('\\', '|'));
            _validChars.Add(Keys.OemSemicolon, new ValidCharacter(';', ':'));
            _validChars.Add(Keys.OemQuotes, new ValidCharacter('\'', '@'));
            _validChars.Add(Keys.OemComma, new ValidCharacter(',', '<'));
            _validChars.Add(Keys.OemPeriod, new ValidCharacter('.', '>'));
            _validChars.Add(Keys.OemQuestion, new ValidCharacter('/', '?'));
        }
        #endregion

        #region Graphics Content
        /// <summary>
        /// Loads the console's graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            GraphicsDevice graphics = Game.GraphicsDevice;
            int prefixHeight;

            _spriteBatch = new SpriteBatch(Game.GraphicsDevice);
            _font = Game.Content.Load<SpriteFont>(@"Fonts/Console");

            _texture = new ResolveTexture2D(graphics,
                graphics.Viewport.Width,
                graphics.Viewport.Height,
                1,
                graphics.DisplayMode.Format);

            _width = graphics.Viewport.Width;
            _height = graphics.Viewport.Height / 2 - 100;

            // Figure out our ideal spacing to reasonably display our text
            prefixHeight = Convert.ToInt32(_font.MeasureString(_prefix).Y);

            if (_height < prefixHeight * 3)
                _height = prefixHeight * 3;

            _validDisplayHeight = _height / prefixHeight - 2;

            if (_validDisplayHeight < 1)
                _validDisplayHeight = 1;

            base.LoadContent();
        }
        #endregion

        #region Update and Draw
        /// <summary>
        /// Updates the console component.
        /// </summary>
        /// <param name="gameTime">The elapsed game time since the last update.</param>
        public override void Update(GameTime gameTime)
        {
            CheckInput();

            base.Update(gameTime);
        }

        /// <summary>
        /// Checks if new input has been received.
        /// </summary>
        private void CheckInput()
        {
            if (_inputManager != null)
            {
                // Toggle the console menu on or off
                if (_inputManager.IsKeyPress(Keys.Tab))
                {
                    _active = !_active;

                    // Clears the log when the menu closes
                    if (_active == false)
                    {
                        _log.Clear();
                        _log.Add(_message);
                    }
                }
                else if (_active == true)
                {
                    bool shiftDown = _inputManager.IsKeyDown(Keys.LeftShift) ||
                        _inputManager.PreviousState.IsKeyDown(Keys.RightShift);
                    Keys[] keysPressed = _inputManager.CurrentState.GetPressedKeys();

                    // No need to do further processing if no keys were pressed
                    if (keysPressed.Length > 0)
                    {
                        // Process valid keys just pressed
                        foreach (Keys key in keysPressed)
                        {
                            if (_validChars.ContainsKey(key) &&
                                _inputManager.PreviousState.IsKeyUp(key))
                            {
                                ValidCharacter ch = (ValidCharacter)_validChars[key];

                                if (shiftDown)
                                    _command += ch.ShiftedCharacter;
                                else
                                    _command += ch.Character;
                            }
                        }

                        // Check input for up arrow (scrolls log up)
                        if (_inputManager.IsKeyPress(Keys.Up))
                        {
                            if (_logOffset < _log.Count - 1)
                                _logOffset++;
                        }

                        // Check input for down arrow (scrolls log down)
                        if (_inputManager.IsKeyPress(Keys.Down))
                        {
                            if (_logOffset > 0)
                                _logOffset--;
                        }

                        // Check input for page up (scrolls log up)
                        if (_inputManager.IsKeyPress(Keys.PageUp))
                        {
                            _logOffset += _validDisplayHeight;

                            if (_logOffset >= _log.Count)
                                _logOffset = _log.Count - 1;
                        }

                        // Check input for page down (scrolls log down)
                        if (_inputManager.IsKeyPress(Keys.PageDown))
                        {
                            _logOffset -= _validDisplayHeight;

                            if (_logOffset < 0)
                                _logOffset = 0;
                        }

                        // Check input for backspace (deletes input)
                        if (_inputManager.IsKeyPress(Keys.Back) &&
                            _command != "" && _command != _prefix)
                        {
                            _command = _command.Remove(_command.Length - 1, 1);
                        }

                        // Check input for enter (enters new command)
                        if (_inputManager.IsKeyPress(Keys.Enter) &&
                            _command != "")
                        {
                            _logOffset = 0;

                            _log.Add("");
                            _log.Add(_prefix + _command);

                            try
                            {
                                if (_lua != null)
                                {
                                    if (_lua.IsRunning)
                                    {
                                        try
                                        {
                                            _lua.Lua.DoString(_command);
                                        }
                                        catch(LuaException ex)
                                        {
                                            _log.Add("ERROR: Command Does Not Exist!");
                                            _log.Add("LUA EXCEPTION: " + ex.Message);
                                        }
                                    }
                                    else
                                        _log.Add("ERROR: Lua Virtual Machine is no longer running.");
                                }
                                else
                                    _log.Add("ERROR: Lua Virtual Machine has not been set.");
                            }
                            catch (LuaException e)
                            {
                                _log.Add("Lua Error: " + e.Message);
                            }

                            _command = "";
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Draws the console component window.
        /// </summary>
        /// <param name="gameTime">The game time elapsed since the last draw call.</param>
        public override void Draw(GameTime gameTime)
        {
            if (_active == true)
            {
                float prefixHeight = _font.MeasureString(_prefix).Y;
                float drawHeight;

                // Saves a copy of the current screen into the texture.  This allows the menus to be transparent
                Game.GraphicsDevice.ResolveBackBuffer(_texture);

                // Without restoring the states properly, the alpha blending will cause some problems
                _spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState);

                // Draw the full background texture
                _spriteBatch.Draw(_texture,
                    new Rectangle(0, 0, Game.GraphicsDevice.Viewport.Width, Game.GraphicsDevice.Viewport.Height),
                    Color.White);

                // Draw transparent menu
                _spriteBatch.Draw(_texture,
                    new Rectangle(0, 0, _width, _height),
                    new Rectangle(0, 0, _width, _height),
                    Color.Gray);

                // Draw command string
                _spriteBatch.DrawString(_font,
                    _line + _prefix + _command,
                    new Vector2(10f, _height - (prefixHeight * 2f) - 4f),
                    Color.White);

                // Draw log
                for (int i = 0; i < _log.Count; i++)
                {
                    drawHeight = _height - 34 + (prefixHeight * i) - (prefixHeight * _log.Count) +
                        (_logOffset * prefixHeight);

                    if (drawHeight + prefixHeight > 0 && drawHeight < _validDisplayHeight * prefixHeight)
                    {
                        _spriteBatch.DrawString(_font,
                            _log[i],
                            new Vector2(10f, drawHeight),
                            Color.Silver);
                    }
                }

                _spriteBatch.End();
            }

            base.Draw(gameTime);
        }
        #endregion

        #region Helper Functions
        /// <summary>
        /// Displays help for the LuaPackageDescriptor to the output window.
        /// </summary>
        /// <param name="package">The package to display help for.</param>
        public void DisplayPackageHelp(LuaPackageDescriptor package)
        {
            IDictionaryEnumerator functions = package.Functions.GetEnumerator();
            string[] results = new string[package.Functions.Count];
            int counter = 0;

            _log.Add(package.PackageName + " - " + package.PackageDoc);
            _log.Add("");
            _log.Add("Available commands:");

            // Display functions within the package
            while (functions.MoveNext())
            {
                results[counter] = ((LuaFunctionDescriptor)functions.Value).GetFunctionHeader();
                counter++;
            }

            Array.Sort(results);

            for (int i = 0; i < counter; i++)
                _log.Add(results[i]);
        }
        #endregion

        #region Lua Methods

        [LuaFunctionAttribute("help", "List available commands.")]
        public void Help()
        {
            string[] results;
            int counter;

            // Display stand-alone commands
            if (_lua.Functions.Count > 0)
            {
                results = new string[_lua.Functions.Count];
                counter = 0;

                _log.Add("");
                _log.Add("Available commands:");

                IDictionaryEnumerator funcs = _lua.Functions.GetEnumerator();

                while (funcs.MoveNext())
                {
                    results[counter] = ((LuaFunctionDescriptor)funcs.Value).GetFunctionHeader();
                    counter++;
                }

                Array.Sort(results);

                for (int i = 0; i < counter; i++)
                    _log.Add(results[i]);
            }

            // Display packages
            if (_lua.Packages.Count > 0)
            {
                results = new string[_lua.Packages.Count];
                counter = 0;

                _log.Add("");
                _log.Add("Available packages:");

                IDictionaryEnumerator pkgs = _lua.Packages.GetEnumerator();

                while (pkgs.MoveNext())
                {
                    LuaPackageDescriptor desc = (LuaPackageDescriptor)pkgs.Value;

                    results[counter] += desc.PackageName + " - " + desc.PackageDoc;
                    counter++;
                }

                Array.Sort(results);

                for (int i = 0; i < counter; i++)
                    _log.Add(results[i]);
            }
        }

        [LuaFunctionAttribute("helpcmd", "Show help for a given command.",
             "Command to get help of (MAKE SURE to put in quotes!).")]
        public void Help(string command)
        {
            _log.Add("");

            // Check if the command is a function name
            if (_lua.Functions.Contains(command))
            {
                LuaFunctionDescriptor func = (LuaFunctionDescriptor)_lua.Functions[command];

                // Displaying tab characters will cause exceptions in the font, so replace them with spaces
                string doc = func.FullDocumentString.Replace("\t", "    ");
                string[] docParts = doc.Split('\n');

                // Put each line in the documentation as a new log entry
                foreach (string s in docParts)
                    if (s.Trim().Length > 0)
                        _log.Add(s);

                return;
            }

            // Check if the command is a package name
            if (command.IndexOf(".") == -1)
            {
                // Check if the package exists
                if (_lua.Packages.ContainsKey(command))
                {
                    LuaPackageDescriptor pkg = (LuaPackageDescriptor)_lua.Packages[command];

                    DisplayPackageHelp(pkg);

                    return;
                }
                else
                {
                    _log.Add("No such function or package: " + command);

                    return;
                }
            }

            // Determine the path to the function name
            string[] parts = command.Split('.');

            // Check if the package exists
            if (!_lua.Packages.ContainsKey(parts[0]))
            {
                _log.Add("No such function or package: " + command);

                return;
            }

            LuaPackageDescriptor desc = (LuaPackageDescriptor)_lua.Packages[parts[0]];

            // Check if the function exists within the package
            if (!desc.HasFunction(parts[1]))
            {
                _log.Add("Package " + parts[0] + " doesn't have a " + parts[1] + " function.");

                return;
            }

            _log.Add(desc.PackageName + "." + desc.ShowHelp(parts[1]));
        }

        [LuaFunctionAttribute("print", "Displays the specified text string to the output window.",
             "The text to be displayed.")]
        public void Print(string text)
        {
            if (!string.IsNullOrEmpty(text))
            {
                _log.Add("");
                _log.Add(text);
            }
            else
            {
                _log.Add("");
                _log.Add("Print Parameter Cannot Be Null Or Empty!");
            }
        }

        [LuaFunctionAttribute("Toggle", "Turns a component on or off.",
           "The component to toggle on or off.")]
        public void ToggleComponent(DrawableGameComponent component)
        {
            component.Enabled = !component.Enabled;
            component.Visible = !component.Visible;
        }

        [LuaFunctionAttribute("Close", "Closes the console!")]
        public void Close()
        {
            _active = false;
        }

        [LuaFunctionAttribute("Quit", "Quits The Game")]
        public void Quit()
        {
            Game.Exit();
        }

        #endregion
    }
}